﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml;
using Notoric.Model.Collections;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// Represents one staff of a system.
    /// </summary>
    /// <remarks>
    /// <para>
    /// In standard Western musical notation, the staff or stave is a set of five horizontal lines and four spaces, 
    /// each of which represents a different musical pitch, or, in the case of a percussion staff, different percussion instruments. 
    /// Appropriate music symbols, depending upon the intended effect, are placed on the staff according to their corresponding pitch 
    /// or function. Musical notes are placed by pitch, percussion notes are placed by instrument, and rests and other symbols are placed by convention.
    /// </para>
    /// <para>
    /// The absolute pitch of each line for a non-percussive stave is determined by the placement of an appropriate <see cref="Clef"/> symbol 
    /// at the appropriate vertical position on the left-hand side of the staff. For example, the treble clef, also known as the G clef, 
    /// is placed upon the second line (counting upwards), fixing that line as the pitch first G above 'middle C'.
    /// </para>
    /// <para>
    /// The lines and spaces are numbered from bottom to top; the bottom line is the first line and the top line is the fifth line.
    /// </para>
    /// <para>
    /// The musical staff is analogous to a mathematical graph of pitch with respect to time. Pitches of notes are given by their vertical 
    /// position on the staff and notes to the left are played before notes to the right. Unlike a graph, however, the exact timing 
    /// of the beginning of each note is not directly proportional to its horizontal position; rather, exact timing is encoded 
    /// by the musical symbol chosen for each note in addition to the tempo.
    /// </para>
    /// <para>
    /// <i>Source: <a href="http://en.wikipedia.org/wiki/Staff_(music))">Wikipedia</a></i>
    /// </para>
    /// </remarks>
    public class Staff : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Staff"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="System"/>
        /// </para>
        /// <para>
        /// The system that directly owns this staff.
        /// </para>
        /// </param>
        /// <param name="descriptor">
        /// <para>
        /// Type: <see cref="StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The descriptor of the staff.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal Staff(XmlPackagePart packagePart, System owner, StaffDescriptor descriptor)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (descriptor == null)
                throw new ArgumentNullException("descriptor");

            this.descriptor = descriptor;

            descriptorAttribute.Value = descriptor.ID.ToString();
            partDescriptorAttribute.Value = descriptor.FindOwner<PartDescriptorElement>().ID.ToString();

            Color = descriptor.DefaultColor;
            Height = descriptor.DefaultHeight;
            Top = 0.0;

            symbolsCollection = new ElementCollection<CoreSymbol>(this, symbolsElement);
            symbols = new ReadOnlyCollection<CoreSymbol>(symbolsCollection);

            attachments = new ElementCollection<Attachment>(this, attachmentsElement);

            measuresIndex = new SortedList<MeasureElement, MeasuresIndexItem>();

            var system = FindOwner<System>();
            var measure = system.Measures[0];
            var firstBeat = measure.GetOrCreateBeat(descriptor.FindOwner<PartDescriptorElement>(), 0);

            symbolsCollection.Add(new Clef(PackagePart, this, ClefType.G, measure, firstBeat));
            symbolsCollection.Add(new KeySignature(PackagePart, this, measure, firstBeat));

            var fitem = new MeasuresIndexItem();
            fitem.Env = new Environment(FirstClef, FirstKeySignature);
            fitem.SymbolsIndex = 0;
            measuresIndex[measure] = fitem;

            for (int i = 1; i < system.Measures.Count; ++i)
            {
                var mitem = new MeasuresIndexItem();
                mitem.Env = new Environment(FirstClef, FirstKeySignature);
                mitem.SymbolsIndex = 2;
                measuresIndex[system.Measures[i]] = mitem;
            }

            var borderRequest = new Internal.StaffBorderRequest(Internal.StaffBorderRequest.PULL_ENVIRONMENT, Internal.Direction.Left, descriptor);
            system.ProcessBorderRequest(borderRequest);
            if (borderRequest.BorderEnvironment != null)
            {
                FirstClef.StaffPosition = borderRequest.BorderEnvironment.CurrentClef.StaffPosition;
                FirstClef.Type = borderRequest.BorderEnvironment.CurrentClef.Type;
                FirstKeySignature.CopyKey(borderRequest.BorderEnvironment.CurrentKey);                  // This line must follow the creation of the measures index, since CopyKey calls GetEnvironmentFor
            }
            else
            {
                FirstClef.StaffPosition = descriptor.DefaultClefPosition;
                FirstClef.Type = descriptor.DefaultClefType;
            }

            PresentKeySignatures();

            foreach (MeasureElement m in system.Measures)
                PresentMeasure(m);

            PresentTimeSignatures();

            FirstClef.EnvironmentChanged += new EventHandler(Symbol_EnvironmentChanged);
            FirstKeySignature.EnvironmentChanged += new EventHandler(Symbol_EnvironmentChanged);
            descriptor.PropertyChanged += new global::System.ComponentModel.PropertyChangedEventHandler(descriptor_PropertyChanged);
            system.Measures.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(Measures_CollectionChanged);
            descriptor.FindOwner<PartDescriptorElement>().KeyChanged += new EventHandler<KeyChangedEventArgs>(Staff_KeyChanged);
        }

        /// <summary>
        /// Creates a new <see cref="Staff"/> instance.
        /// </summary>
        /// <remarks>
        /// The <see cref="Attachments"/> collection is deserialized first. Therefore the deserialization of references to <see cref="CoreSymbol"/>s
        /// needs to be delayed, otherwise the deserialization of the staff will fail.
        /// </remarks>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="System"/>
        /// </para>
        /// <para>
        /// The system that directly owns this staff.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal Staff(XmlPackagePart packagePart, System owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            colorAttribute = element.Attributes[PackageVocabulary.ColorAttribute];
            descriptorAttribute = element.Attributes[PackageVocabulary.StaffDescriptorAttribute];
            heightAttribute = element.Attributes[PackageVocabulary.HeightAttribute];
            partDescriptorAttribute = element.Attributes[PackageVocabulary.PartDescriptorAttribute];
            topAttribute = element.Attributes[PackageVocabulary.TopAttribute];

            var pdid = new Guid(partDescriptorAttribute.Value);
            var sdid = new Guid(descriptorAttribute.Value);
            var section = FindOwner<MusicSectionElement>();
            var pdRef = section.Parts.FirstOrDefault(pd => pd.ID == pdid);

            if (pdRef == null)
                throw new SerializationException("Could not find the part descriptor ID " + pdid + " among presented parts!", element.OuterXml);

            var cwData = (CWPartData)pdRef.CustomData.FirstOrDefault(cd => cd is CWPartData);

            if (cwData == null)
                throw new SerializationException("The part descriptor ID " + pdid + " does not contain the CW Notation extension!", element.OuterXml);

            descriptor = cwData.Staves.FirstOrDefault(sd => sd.ID == sdid);

            if (descriptor == null)
                throw new SerializationException("The staff descriptor ID " + sdid + " not found among the part staves!", element.OuterXml);

            var lines = descriptor.Lines;
            if (lines > 1)
                spaceSize = double.Parse(heightAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat) / (lines - 1);
            else
                spaceSize = 0.0;

            attachmentsElement = element[PackageVocabulary.AttachmentElementName + "s", PackageVocabulary.PackageNamespaceUri];
            attachments = new ElementCollection<Attachment>(this, attachmentsElement);

            symbolsElement = element[PackageVocabulary.CoreSymbolElementName + "s", PackageVocabulary.PackageNamespaceUri];
            symbolsCollection = new ElementCollection<CoreSymbol>(this, symbolsElement);
            symbols = new ReadOnlyCollection<CoreSymbol>(symbolsCollection);

            if (symbolsCollection.Count < 2 || !(symbolsCollection[0] is Clef) || !(symbolsCollection[1] is KeySignature))
                throw new SerializationException("The staff is not valid, it does contain the first clef and key signature!", element.OuterXml);

            var system = FindOwner<System>();            
            
            measuresIndex = new SortedList<MeasureElement, MeasuresIndexItem>();
            foreach (var measure in system.Measures)
            {
                var mitem = new MeasuresIndexItem();
                measuresIndex[measure] = mitem;

                var part = measure.Parts.FirstOrDefault(p => p.Descriptor == pdRef);
                if (part != null)
                    part.Content.MusicalContentChanged += new EventHandler<MusicalContentChangedEventArgs>(Part_MusicalContentChanged);
            }

            measuresIndex[system.Measures[0]].Env = new Environment(FirstClef, FirstKeySignature);

            var actualMeasure = system.Measures[0];
            var lastClef = FirstClef;
            var lastKey = FirstKeySignature;
            for (int i = 0; i < symbolsCollection.Count; ++i)
            {
                MeasureElement measure;
                Clef clef = lastClef;
                KeySignature key = lastKey;

                symbolsCollection[i].EnvironmentChanged += new EventHandler(Symbol_EnvironmentChanged);

                if (symbolsCollection[i] is DurationalSymbol)
                    measure = ((DurationalSymbol)symbolsCollection[i]).PresentedElement.FindOwner<MeasureElement>();
                else
                {
                    measure = ((EnvironmentModifier)symbolsCollection[i]).Measure;

                    if (symbolsCollection[i] is Clef)
                        clef = (Clef)symbolsCollection[i];
                    else if (symbolsCollection[i] is KeySignature)
                        key = (KeySignature)symbolsCollection[i];
                }

                if (measure != actualMeasure)
                {
                    var mitem = measuresIndex[measure];
                    mitem.Env = new Environment(lastClef, lastKey);
                    mitem.SymbolsIndex = i;

                    if (measure.Number > actualMeasure.Number + 1)
                    {
                        for (int index = measuresIndex.IndexOfKey(actualMeasure) + 1; index < measuresIndex.Count; ++index)
                        {
                            mitem = measuresIndex.Values[index];
                            mitem.Env = new Environment(lastClef, lastKey);
                            mitem.SymbolsIndex = i;
                        }
                    }

                    actualMeasure = measure;
                }

                lastClef = clef;
                lastKey = key;
            }

            for (int index = measuresIndex.IndexOfKey(actualMeasure) + 1; index < measuresIndex.Count; ++index)
            {
                var mitem = measuresIndex.Values[index];
                mitem.Env = new Environment(lastClef, lastKey);
                mitem.SymbolsIndex = symbolsCollection.Count;
            }

            system.Measures.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(Measures_CollectionChanged);
            descriptor.PropertyChanged += new PropertyChangedEventHandler(descriptor_PropertyChanged);
            pdRef.KeyChanged += new EventHandler<KeyChangedEventArgs>(Staff_KeyChanged);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.StaffElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            colorAttribute = document.CreateAttribute(PackageVocabulary.ColorAttribute);
            descriptorAttribute = document.CreateAttribute(PackageVocabulary.StaffDescriptorAttribute);
            heightAttribute = document.CreateAttribute(PackageVocabulary.HeightAttribute);
            partDescriptorAttribute = document.CreateAttribute(PackageVocabulary.PartDescriptorAttribute);
            topAttribute = document.CreateAttribute(PackageVocabulary.TopAttribute);

            attachmentsElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.AttachmentElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );
            symbolsElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.CoreSymbolElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );

            element.AppendChild(symbolsElement);
            element.AppendChild(attachmentsElement);

            element.Attributes.Append(colorAttribute);
            element.Attributes.Append(descriptorAttribute);
            element.Attributes.Append(heightAttribute);
            element.Attributes.Append(partDescriptorAttribute);
            element.Attributes.Append(topAttribute);

            return element;
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the value of some property of the descriptor has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the modified property is <see cref="StaffDescriptor.Lines"/> the handler recomputes the <see cref="SpaceSize"/> value.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void descriptor_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Lines")
            {
                if (descriptor.Lines > 1)
                    spaceSize = Height / (descriptor.Lines - 1);
                else
                    spaceSize = 0.0;

                RaisePropertyChanged("SpaceSize");
            }
        }

        /// <summary>
        /// Handles the KeyChanged event of the part descriptor..
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Notoric.Model.Elements.KeyChangedEventArgs"/> instance containing the event data.</param>
        private void Staff_KeyChanged(object sender, KeyChangedEventArgs e)
        {
            if (e.FromMeasure == null || e.ToMeasure == null)
                return;

            var system = FindOwner<System>();
            if (e.FromMeasure.Number > system.Measures.Last().Number || e.ToMeasure.Number < system.Measures.First().Number)
                return;

            var firstMeasure = (e.FromMeasure.Number < system.Measures.First().Number ? system.Measures.First() : e.FromMeasure);
            var lastMeasure = (e.ToMeasure.Number > system.Measures.Last().Number ? system.Measures.Last() : e.ToMeasure);
            var beat = firstMeasure.GetOrCreateBeat(descriptor.FindOwner<PartDescriptorElement>(), 0);

            var mitemF = measuresIndex[firstMeasure];
            var mindexL = measuresIndex.IndexOfKey(lastMeasure);
            
            var stopIndex= symbolsCollection.Count;
            if (mindexL < measuresIndex.Count - 1)
                stopIndex = measuresIndex.Values[mindexL].SymbolsIndex;

            var startIndex = mitemF.SymbolsIndex;
            KeySignature signature = new KeySignature(PackagePart, this, firstMeasure, beat);
            signature.EnvironmentChanged += new EventHandler(Symbol_EnvironmentChanged);
            
            if (startIndex < symbolsCollection.Count && symbolsCollection[startIndex] is Clef)
                ++startIndex;
            symbolsCollection.Insert(startIndex++, signature);

            if (startIndex == 2)
                mitemF.Env.CurrentKey = signature;

            if (e.Key != null)
            {
                for (byte i = 1; i <= 7; ++i)
                {
                    var accidental = Accidental.AccidentalValueToType(e.Key[i]);
                    if (e.Key[i] == 0.0)
                        accidental = AccidentalType.None;

                    signature.SetAccidental(i, accidental);
                }
            }

            var currentMeasure = firstMeasure;
            var diff = 1;

            while (startIndex < stopIndex + diff)
            {
                if (symbolsCollection[startIndex].Measure != currentMeasure)
                {
                    var mitem = measuresIndex[symbolsCollection[startIndex].Measure];
                    mitem.SymbolsIndex += diff;
                    
                    currentMeasure = symbolsCollection[startIndex].Measure;
                }
                if (symbolsCollection[startIndex] is KeySignature)
                {
                    symbolsCollection[startIndex].EnvironmentChanged -= Symbol_EnvironmentChanged;
                    symbolsCollection.RemoveAt(startIndex);
                    --diff;
                }
                else
                    ++startIndex;
            }

            if (currentMeasure.Number < system.Measures.Last().Number)
            {
                var mindex = measuresIndex.IndexOfKey(currentMeasure);
                for (int i = mindex + 1; i < measuresIndex.Count; ++i)
                    measuresIndex.Values[i].SymbolsIndex += diff;
            }

            signature.RaiseEnvironmentChanged();
        }

        /// <summary>
        /// Occurs when the <see cref="System.Measures"/> collection of the owner <see cref="System"/> has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operations:</b><br/>
        /// If measures were added, the handler creates entries for them in the <see cref="measuresIndex"/>
        /// and presents the by calling the <see cref="PresentMeasure"/> method.<br/>
        /// If measures were removed, their symbols are removed from the <see cref="Symbols"/> collection
        /// and their entries are removed from the <see cref="measuresIndex"/>.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Measures_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                {
                    foreach (MeasureElement measure in e.NewItems)
                    {
                        var mitem = new MeasuresIndexItem();
                        measuresIndex[measure] = mitem;

                        var mindex = measuresIndex.IndexOfKey(measure);
                        if (mindex < measuresIndex.Count - 1)
                        {
                            mitem.SymbolsIndex = measuresIndex.Values[mindex + 1].SymbolsIndex;
                            mitem.Env = new Environment(measuresIndex.Values[mindex + 1].Env);
                        }
                        else
                        {
                            mitem.SymbolsIndex = symbolsCollection.Count;
                            if (mindex > 0)
                            {
                                mitem.Env = new Environment(measuresIndex.Values[mindex - 1].Env);

                                for (int i = measuresIndex.Values[mindex - 1].SymbolsIndex; i < symbolsCollection.Count; ++i)
                                {
                                    if (symbolsCollection[i] is Clef)
                                        mitem.Env.CurrentClef = symbolsCollection[i] as Clef;
                                    if (symbolsCollection[i] is KeySignature)
                                        mitem.Env.CurrentKey = symbolsCollection[i] as KeySignature;
                                }
                            }
                            else
                                mitem.Env = new Environment(FirstClef, FirstKeySignature);
                        }

                        PresentMeasure(measure);
                    }
                    break;
                }
                case NotifyCollectionChangedAction.Remove:
                {
                    foreach (MeasureElement measure in e.OldItems)
                    {
                        int startIndex = measuresIndex[measure].SymbolsIndex;
                        int stopIndex = symbolsCollection.Count;
                        int diff = stopIndex - startIndex;
                        int mindex = measuresIndex.IndexOfKey(measure);
                        if (mindex < measuresIndex.Count - 1)
                            stopIndex = measuresIndex.Values[mindex + 1].SymbolsIndex;

                        for (int i = stopIndex - 1; i >= startIndex; --i)
                        {
                            if (symbolsCollection[i] is Clef || symbolsCollection[i] is KeySignature)
                                Symbol_EnvironmentChanged(symbolsCollection[i], EventArgs.Empty);
                            RemoveSymbolAttachments(symbolsCollection[i]);
                            symbolsCollection.RemoveAt(i);
                        }

                        for (int i = mindex + 1; i < measuresIndex.Count; ++i)
                            measuresIndex.Values[i].SymbolsIndex -= diff;

                        measuresIndex.Remove(measure);
                    }
                    break;
                }
                case NotifyCollectionChangedAction.Reset:
                    attachments.Clear();
                    symbolsCollection.Clear();
                    measuresIndex.Clear();
                    break;
            }

            PresentTimeSignatures();
        }

        /// <summary>
        /// Occurs when the presented musical content has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        internal void Part_MusicalContentChanged(object sender, MusicalContentChangedEventArgs e)
        {
            if (e.ModifiedCollection == MusicalContentChangedCollection.DurationalSymbols)
            {
                if (!IsMySymbol(e.AffectedSymbol))
                    return;

                var measure = ((MusicalContentElement)sender).FindOwner<MeasureElement>();
                var mIndex = measuresIndex.IndexOfKey(measure);
                var mitem = measuresIndex.Values[mIndex];
                int stopIndex = symbolsCollection.Count;
                if (mIndex < measuresIndex.Count - 1)
                    stopIndex = measuresIndex.Values[mIndex + 1].SymbolsIndex;

                if (e.Action == MusicalContentChangedAction.Remove)
                {
                    for (int i = mitem.SymbolsIndex; i < stopIndex; ++i)
                    {
                        if (symbolsCollection[i] is DurationalSymbol)
                        {
                            if (((DurationalSymbol)symbolsCollection[i]).PresentedElement == e.AffectedSymbol)
                            {
                                symbolsCollection.RemoveAt(i);
                                break;
                            }
                        }
                    }

                    for (int i = mIndex + 1; i < measuresIndex.Count; ++i)
                        --measuresIndex.Values[i].SymbolsIndex;
                }
                else if (e.Action == MusicalContentChangedAction.Add)
                {
                    int i = mitem.SymbolsIndex;
                    for (; i < stopIndex; ++i)
                    {
                        if (symbolsCollection[i] is DurationalSymbol)
                        {
                            var presentedElement = ((DurationalSymbol)symbolsCollection[i]).PresentedElement;
                            if (presentedElement.FindOwner<BeatElement>().Beginning > e.AffectedSymbol.FindOwner<BeatElement>().Beginning)
                                break;
                        }
                    }

                    PresentDurationalSymbol(e.AffectedSymbol, i);

                    for (i = mIndex + 1; i < measuresIndex.Count; ++i)
                        ++measuresIndex.Values[i].SymbolsIndex;
                }
            }
            else
            {
                if (!IsMyAttachment(e.AffectedAttachment))
                    return;

                if (e.Action == MusicalContentChangedAction.Add)
                    attachments.Add(PresentedAttachment.CreateRepresentation(PackagePart, this, e.AffectedAttachment));
                else
                {
                    var attachment = attachments.First(a => a is PresentedAttachment && ((PresentedAttachment)a).PresentedElement == e.AffectedAttachment);
                    attachments.Remove(attachment);
                }
            }
        }

        /// <summary>
        /// Occurs when some core symbol caused a change in the staff environment.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler calls the <see cref="CoreSymbol.ProcessEnvironmentChange"/> on all core symbols
        /// that follow the affected one.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Symbol_EnvironmentChanged(object sender, EventArgs e)
        {
            var symbol = (CoreSymbol)sender;
            var mitem = measuresIndex[symbol.Measure];
            var env = new Environment(mitem.Env.CurrentClef, mitem.Env.CurrentKey);
            var startIndex = mitem.SymbolsIndex;
            var stopIndex = symbolsCollection.Count;

            if (symbol is DurationalSymbol)
            {
                var mindex = measuresIndex.IndexOfKey(symbol.Measure);
                if (mindex + 1 < measuresIndex.Count)
                    stopIndex = measuresIndex.Values[mindex + 1].SymbolsIndex;
            }

            var currentMeasure = symbol.Measure;

            for (int i = startIndex; i < stopIndex; ++i)
            {
                if (symbolsCollection[i].Measure != currentMeasure)
                {
                    currentMeasure = symbol.Measure;
                    measuresIndex[currentMeasure].Env = new Environment(env);
                }
                
                symbolsCollection[i].ProcessEnvironmentChange(env);

                if (symbolsCollection[i] is Chord)
                    foreach (var note in ((Chord)symbolsCollection[i]).Notes)
                        env.AddToLastNotes(note);
                else if (symbolsCollection[i] is Clef)
                    env.CurrentClef = symbolsCollection[i] as Clef;
                else if (symbolsCollection[i] is KeySignature)
                    env.CurrentKey = symbolsCollection[i] as KeySignature;
            }

            if (currentMeasure.Number < FindOwner<System>().Measures.Last().Number && !(symbol is DurationalSymbol))
            {
                var mIndex = measuresIndex.IndexOfKey(currentMeasure) + 1;
                for (; mIndex < measuresIndex.Count; ++mIndex)
                    measuresIndex.Values[mIndex].Env = new Environment(env);
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Determines whether a given attachment is (should be) presented on this staff or not.
        /// </summary>
        /// <param name="attachment">
        /// <para>
        /// Type: <see cref="AttachmentElement"/>
        /// </para>
        /// <para>
        /// The examined attachment.
        /// </para>
        /// </param>
        /// <returns>
        /// <see langword="true"/> if the attachment is (should be) presented on this staff; otherwise, <see langword="false"/>.
        /// </returns>
        private bool IsMyAttachment(AttachmentElement attachment)
        {
            if (attachment.Voices.Count == 0)
                return descriptor == descriptor.FindOwner<CWPartData>().Staves.Last();

            int staffIndex = descriptor.FindOwner<CWPartData>().Staves.IndexOf(descriptor);
            return attachment.Voices.Any(v => v.Number >= staffIndex * 3 && v.Number < (staffIndex + 1) * 3);
        }

        /// <summary>
        /// Determines whether a given symbol is (shoul be) presented on this staff or not.
        /// </summary>
        /// <param name="symbol">The examined symbol</param>
        /// <returns><see langword="true"/> if the symbol is (should be) presented on this staff, <see langword="false"/> otherwise.</returns>
        private bool IsMySymbol(DurationalSymbolElement symbol)
        {
            var cwData = symbol.FindCustomData<CWSymbolData>();

            if (cwData != null)
                return descriptor == cwData.MyStaff;

            int staffIndex = descriptor.FindOwner<CWPartData>().Staves.IndexOf(descriptor);
            return symbol.Voice.Number >= staffIndex * 3 && symbol.Voice.Number < (staffIndex + 1) * 3;
        }

        /// <summary>
        /// Presents the key signatures.
        /// </summary>
        private void PresentKeySignatures()
        {
            var partDescriptor = Descriptor.FindOwner<PartDescriptorElement>();

            var key = partDescriptor.KeyChangements.LastOrDefault(kc => kc.Measure.Number < FindOwner<System>().Measures[0].Number);
            if (key != null)
            {
                for (byte i = 1; i <= 7; ++i)
                {
                    var accidental = Accidental.AccidentalValueToType(key.KeyIndication[i]);
                    if (key.KeyIndication[i] == 0.0)
                        accidental = AccidentalType.None;
                    FirstKeySignature.SetAccidental(i, accidental);
                }
            }
            else
            {
                for (byte i = 1; i <= 7; ++i)
                {
                    var accidental = Accidental.AccidentalValueToType(partDescriptor.KeyIndication[i]);
                    if (partDescriptor.KeyIndication[i] == 0.0)
                        accidental = AccidentalType.None;
                    FirstKeySignature.SetAccidental(i, accidental);
                }
            }

            foreach (var keyChange in partDescriptor.KeyChangements)
            {
                var mindex = measuresIndex.IndexOfKey(keyChange.Measure);
                if (mindex != -1)
                {
                    var symbolsIndex = measuresIndex.Values[mindex].SymbolsIndex;
                    var keySignature = new KeySignature(PackagePart, this, keyChange.Measure, keyChange.Measure.GetOrCreateBeat(partDescriptor, 0));
                    keySignature.EnvironmentChanged += new EventHandler(Symbol_EnvironmentChanged);
                    for (byte i = 1; i <= 7; ++i)
                    {
                        var accidental = Accidental.AccidentalValueToType(keyChange.KeyIndication[i]);
                        if (keyChange.KeyIndication[i] == 0.0)
                            accidental = AccidentalType.None;
                        keySignature.SetAccidental(i, accidental);
                    }
                    symbolsCollection.Insert(symbolsIndex, keySignature);

                    for (int i = mindex + 1; i < measuresIndex.Count; ++i)
                        ++measuresIndex.Values[i].SymbolsIndex;
                }
            }
        }

        /// <summary>
        /// Presents the symbols in a given measure on this staff.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method assumes that no symbol of <paramref name="measure"/> is already presented. 
        /// It also expects to find a valid entry for <paramref name="measure"/> in the <see cref="measuresIndex"/>.<br/>
        /// This method only presents symbols for which the <see cref="IsMySymbol"/> method returns <see langword="true"/>.
        /// </para>
        /// </remarks>
        /// <param name="measure">Presented measure</param>
        private void PresentMeasure(MeasureElement measure)
        {
            var myPart = measure.Parts.FirstOrDefault(p => p.Descriptor == Descriptor.FindOwner<PartDescriptorElement>());
            if (myPart != null)
            {
                var myDurationalSymbols = from beat in myPart.Content.Beats
                                          from symbol in beat.Symbols
                                          where IsMySymbol(symbol)
                                          select symbol;

                int index = measuresIndex[measure].SymbolsIndex;
                if (index < 2) 
                    index = 2;
                int start = index;
                foreach (var symbol in myDurationalSymbols)
                    PresentDurationalSymbol(symbol, index++);

                var mindex = measuresIndex.IndexOfKey(measure);
                if (mindex < measuresIndex.Count - 1)
                {
                    var diff = index - start;
                    for (int i = mindex + 1; i < measuresIndex.Count; ++i)
                        measuresIndex.Values[i].SymbolsIndex += diff;
                }

                var myAttachments = from beat in myPart.Content.Beats
                                    from attachment in beat.Attachments
                                    where IsMyAttachment(attachment)
                                    select attachment;
                foreach (var attachment in myAttachments)
                    attachments.Add(PresentedAttachment.CreateRepresentation(PackagePart, this, attachment));
            }
        }

        /// <summary>
        /// Creates a staff representation for a given symbol at a given index of the <see cref="Symbols"/> collection.
        /// </summary>
        /// <param name="symbol">Presented symbol</param>
        /// <param name="index">Index where the symbol will be inserted</param>
        private void PresentDurationalSymbol(DurationalSymbolElement symbol, int index)
        {
            if (symbol is ChordElement)
            {
                var chord = new Chord(PackagePart, this, (ChordElement)symbol);
                chord.EnvironmentChanged += new EventHandler(Symbol_EnvironmentChanged);
                symbolsCollection.Insert(index, chord);
            }
            else if (symbol is RestElement)
                symbolsCollection.Insert(index, new Rest(PackagePart, this, (RestElement)symbol));
        }

        /// <summary>
        /// When a symbol is removed from the <see cref="Symbols"/> collection this method
        /// checks its attachments and if any becomes unnecessary it removes it from the 
        /// <see cref="Attachments"/> collection.
        /// </summary>
        /// <param name="symbol">Removed core symbol</param>
        private void RemoveSymbolAttachments(CoreSymbol symbol)
        {
            if (symbol is DurationalSymbol)
            {
                var durSym = symbol as DurationalSymbol;
                if (durSym.GroupBeam != null)
                {
                    var beam = durSym.GroupBeam;
                    Beam.SplitGroup(new DurationalSymbol[] { durSym });

                    if (beam.BeamedSymbols.Count == 0)
                        attachments.Remove(beam);
                }
            }
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Handles an incoming border request.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Internal.StaffBorderRequest"/>
        /// </para>
        /// <para>
        /// The request data.
        /// </para>
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The <paramref name="request"/> is <see langword="null"/>.<br/>
        /// -or-<br/>
        /// The request is not recognized.<br/>
        /// -or-<br/>
        /// The request data are not valid.
        /// </exception>
        internal void HandleBorderRequest(Internal.StaffBorderRequest request)
        {
            if (request.Request == Internal.StaffBorderRequest.PULL_ENVIRONMENT)
            {
                if (request.RequestDirection == Internal.Direction.Left)
                {
                    var mi = measuresIndex.Values[measuresIndex.Count - 1];
                    request.BorderEnvironment = new Environment(mi.Env.CurrentClef, mi.Env.CurrentKey);

                    for (int i = mi.SymbolsIndex; i < symbolsCollection.Count; ++i)
                    {
                        if (symbolsCollection[i] is Clef)
                            request.BorderEnvironment.CurrentClef = symbolsCollection[i] as Clef;
                        else if (symbolsCollection[i] is KeySignature)
                            request.BorderEnvironment.CurrentKey = symbolsCollection[i] as KeySignature;
                        else if (symbolsCollection[i] is Chord)
                        {
                            foreach (Note note in ((Chord)symbolsCollection[i]).Notes)
                                request.BorderEnvironment.AddToLastNotes(note);
                        }
                    }
                }
                else
                    request.BorderEnvironment = new Environment(FirstClef, FirstKeySignature);
            }
        }

        /// <summary>
        /// Presents the time signatures.
        /// </summary>
        internal void PresentTimeSignatures()
        {
            var lastTS = FirstTimeSignature;
            var collectionPart = new Utils.CollectionPart<CoreSymbol>(symbolsCollection);

            for (int i = 0; i < measuresIndex.Count; ++i)
            {
                var mitem = measuresIndex.Values[i];
                var next = (i < measuresIndex.Count - 1 ? measuresIndex.Values[i + 1] : null);
                var stopIndex = (next != null ? next.SymbolsIndex : symbolsCollection.Count);
                var measure = measuresIndex.Keys[i];

                if (measure.Meter == null)
                    continue;

                collectionPart.SetBounds(mitem.SymbolsIndex, stopIndex);
                var ts = collectionPart.FirstOrDefault(s => s is TimeSignature) as TimeSignature;
                if (ts != null)
                {
                    if (ts == lastTS && lastTS.Measure != ts.Measure)
                    {
                        symbolsCollection.RemoveAt(IndexOf(ts));
                        for (int j = i + 1; j < measuresIndex.Count; ++j)
                            --measuresIndex.Values[j].SymbolsIndex;
                    }
                    else if (ts.SignatureType != measure.Meter.SignatureType || (ts.SignatureType == measure.Meter.SignatureType && (ts.Denominator != measure.Meter.BeatUnit || ts.Nominator != measure.Meter.Beats)))
                    {
                        symbolsCollection.RemoveAt(IndexOf(ts));
                        for (int j = i + 1; j < measuresIndex.Count; ++j)
                            --measuresIndex.Values[j].SymbolsIndex;
                    }
                    else
                        lastTS = ts;
                }
                else
                {
                    var different = lastTS == null || (lastTS.SignatureType != measure.Meter.SignatureType || 
                        (lastTS.SignatureType == TimeSignatureType.Standard && (lastTS.Denominator != measure.Meter.BeatUnit || lastTS.Nominator != measure.Meter.Beats))
                        );

                    if (different)
                    {
                        var beat = measure.GetOrCreateBeat(Descriptor.FindOwner<PartDescriptorElement>(), 0);
                        ts = new TimeSignature(PackagePart, this, measure.Meter, beat);
                        int j = mitem.SymbolsIndex;
                        for (; j < stopIndex && symbolsCollection[j] is EnvironmentModifier; ++j) ;
                        symbolsCollection.Insert(j, ts);

                        for (j = i + 1; j < measuresIndex.Count; ++j)
                            ++measuresIndex.Values[j].SymbolsIndex;

                        lastTS = ts;
                    }
                }
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adds a new clef at a specified beat to this staff.
        /// </summary>
        /// <param name="beat">
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Clef"/>
        /// </para>
        /// <para>
        /// The created clef.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="beat"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="beat"/> is not presented by this staff.
        /// </exception>
        public Clef AddClef(BeatElement beat)
        {
            if (beat == null)
                throw new ArgumentNullException("beat");

            var measure = beat.FindOwner<MeasureElement>();
            
            if (!measuresIndex.ContainsKey(measure))
                throw new ArgumentException("The staff does not present the specified beat.", "beat");

            var mindex = measuresIndex.IndexOfKey(measure);
            var startIndex = measuresIndex.Values[mindex].SymbolsIndex;
            var stopIndex = symbolsCollection.Count;
            if (mindex < measuresIndex.Count - 1)
                stopIndex = measuresIndex.Values[mindex + 1].SymbolsIndex;

            int clefIndex = startIndex;
            for ( ; clefIndex < stopIndex && symbolsCollection[clefIndex].Beat.Beginning < beat.Beginning; ++clefIndex) ;
            var clef = new Clef(PackagePart, this, ClefType.G, measure, beat);
            symbolsCollection.Insert(clefIndex, clef);

            for (int i = mindex + 1; i < measuresIndex.Count; ++i)
                ++measuresIndex.Values[i].SymbolsIndex;

            clef.EnvironmentChanged += new EventHandler(Symbol_EnvironmentChanged);
            clef.RaiseEnvironmentChanged();

            return clef;
        }

        /// <summary>
        /// Finds the durational symbol on this staff that presents a given durational symbol element.
        /// </summary>
        /// <param name="symbol">
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The durational symbol element whose representant is being searched.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The symbol that presents the given <paramref name="symbol"/> or <see langword="null"/>
        /// if the <paramref name="symbol"/> does not have a representation on this staff.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="symbol"/> is <see langword="null"/>
        /// </exception>
        public DurationalSymbol FindSymbolFor(DurationalSymbolElement symbol)
        {
            if (symbol == null)
                throw new ArgumentNullException("symbol");

            var measure = symbol.FindOwner<MeasureElement>();
            var mIndex = measuresIndex.IndexOfKey(measure);

            if (mIndex < 0)
                return null;

            int stopIndex = symbolsCollection.Count;
            if (mIndex < measuresIndex.Count - 1)
                stopIndex = measuresIndex.Values[mIndex + 1].SymbolsIndex;

            DurationalSymbol result = null;

            for (int i = measuresIndex.Values[mIndex].SymbolsIndex; i < stopIndex; ++i)
            {
                if (symbolsCollection[i] is DurationalSymbol && ((DurationalSymbol)symbolsCollection[i]).PresentedElement == symbol)
                {
                    result = symbolsCollection[i] as DurationalSymbol;
                    break;
                }
            }

            return result;
        }

        /// <summary>
        /// Gets the environment for a given core symbol.
        /// </summary>
        /// <param name="symbol">
        /// <para>
        /// Type: <see cref="CoreSymbol"/>
        /// </para>
        /// <para>
        /// The symbol for which the environment is being determined.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Environment"/>
        /// </para>
        /// <para>
        /// The environment in which the symbol exists.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="symbol"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="symbol"/> does not belong to this staff.
        /// </exception>
        public Environment GetEnvironmentFor(CoreSymbol symbol)
        {
            if (symbol == null)
                throw new ArgumentNullException("symbol");
            if (symbol.FindOwner<Staff>() != this)
                throw new ArgumentException("The symbol does not belong to this staff!", "symbol");

            MeasureElement symbolMeasure;

            if (symbol is DurationalSymbol)
                symbolMeasure = ((DurationalSymbol)symbol).PresentedElement.FindOwner<MeasureElement>();
            else
                symbolMeasure = ((EnvironmentModifier)symbol).Measure;

            var mitem = measuresIndex[symbolMeasure];
            var env = new Environment(mitem.Env.CurrentClef, mitem.Env.CurrentKey);

            for (int i = mitem.SymbolsIndex; i < symbolsCollection.Count && symbolsCollection[i] != symbol; ++i)
            {
                if (symbolsCollection[i] is EnvironmentModifier)
                {
                    if (symbolsCollection[i] is Clef)
                        env.CurrentClef = symbolsCollection[i] as Clef;
                    else if (symbolsCollection[i] is KeySignature)
                        env.CurrentKey = symbolsCollection[i] as KeySignature;
                }
                else
                {
                    if (symbolsCollection[i] is Chord)
                    {
                        foreach (Note note in ((Chord)symbolsCollection[i]).Notes)
                            env.AddToLastNotes(note);
                    }
                }
            }

            return env;
        }
        
        /// <summary>
        /// Gets the environment at the beginning of a given measure..
        /// </summary>
        /// <param name="measure">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The measure.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Environment"/>
        /// </para>
        /// <para>
        /// The environment at the beginning of the measure.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="measure"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="measure"/> is not presented by this staff.
        /// </exception>
        public Environment GetMeasureEnvironment(MeasureElement measure)
        {
            if (measure == null)
                throw new ArgumentNullException("measure");

            MeasuresIndexItem mitem = null;
            if (!measuresIndex.TryGetValue(measure, out mitem))
                throw new ArgumentException("The measure is not presented by this staff.");

            return new Environment(mitem.Env);
        }

        /// <summary>
        /// Gets all the symbols at given beat.
        /// </summary>
        /// <param name="beat">
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="IEnumerable{T}"/> of <see cref="CoreSymbol"/>
        /// </para>
        /// <para>
        /// A collection of all the symbols of this staff that belong to the <paramref name="beat"/>.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="beat"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="beat"/> belongs to a measure that is not presented on this staff.
        /// </exception>
        public IEnumerable<CoreSymbol> GetSymbolsAtBeat(BeatElement beat)
        {
            if (beat == null)
                throw new ArgumentNullException("beat");
            int mIndex = measuresIndex.IndexOfKey(beat.FindOwner<MeasureElement>());
            if (mIndex < 0)
                throw new ArgumentException("The beat is not presented on this staff.");

            var collectionPart = new Utils.CollectionPart<CoreSymbol>(symbolsCollection);
            collectionPart.SetBounds(measuresIndex.Values[mIndex].SymbolsIndex, (mIndex < measuresIndex.Count - 1 ? measuresIndex.Values[mIndex + 1].SymbolsIndex : symbolsCollection.Count));

            return from symbol in collectionPart
                   where symbol.Beat == beat
                   select symbol;
        }

        /// <summary>
        /// Retrieves the index of a given core symbol in the <see cref="Symbols"/> collection.
        /// </summary>
        /// <remarks>
        /// The difference between using this method and the <see cref="IList{T}.IndexOf"/> is that
        /// this method uses the internal symbols indexing of the staff to speed-up the search.
        /// </remarks>
        /// <param name="symbol">
        /// <para>
        /// Type: <see cref="CoreSymbol"/>
        /// </para>
        /// <para>
        /// The symbol whose symbol is being searched.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The index of the symbol in the <see cref="Symbols"/> collection or -1 if the symbol was not found.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="symbol"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="symbol"/> does not belong to this staff.
        /// </exception>
        public int IndexOf(CoreSymbol symbol)
        {
            if (symbol == null)
                throw new ArgumentNullException("symbol");
            if (symbol.FindOwner<Staff>() != this)
                throw new ArgumentException("The symbol does not belong to this staff!");

            var measure = symbol.Measure;
            var mIndex = measuresIndex.IndexOfKey(measure);
            int stopIndex = symbolsCollection.Count;
            if (mIndex < measuresIndex.Count - 1)
                stopIndex = measuresIndex.Values[mIndex + 1].SymbolsIndex;

            int result = -1;

            for (int i = measuresIndex.Values[mIndex].SymbolsIndex; i < stopIndex; ++i)
            {
                if (symbolsCollection[i] == symbol)
                {
                    result = i;
                    break;
                }
            }

            return result;
        }

        /// <summary>
        /// Removes a given clef from the staff.
        /// </summary>
        /// <param name="clef">
        /// <para>
        /// Type: <see cref="Clef"/>
        /// </para>
        /// <para>
        /// The clef.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="clef"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="clef"/> does not belong to this staff.<br/>
        /// -or-<br/>
        /// The <paramref name="clef"/> was already removed.<br/>
        /// -or-<br/>
        /// The <paramref name="clef"/> is the first clef of the staff.
        /// </exception>
        public void RemoveClef(Clef clef)
        {
            if (clef == null)
                throw new ArgumentNullException("clef");
            if (clef.FindOwner<Staff>() != this)
                throw new ArgumentException("The clef does not belong to this staff.", "clef");

            var index = symbolsCollection.IndexOf(clef);
            if (index == -1)
                throw new ArgumentException("The clef was already removed.", "clef");
            if (index == 0)
                throw new ArgumentException("The first clef of the staff cannot be removed. If you want to change it, use its properties.", "clef");

            var mindex = measuresIndex.IndexOfKey(clef.Measure);
            symbolsCollection[index].EnvironmentChanged -= Symbol_EnvironmentChanged;
            symbolsCollection.RemoveAt(index);
            for (int i = mindex + 1; i < measuresIndex.Count; ++i)
                --measuresIndex.Values[i].SymbolsIndex;

            for (; index >= 0 && !(symbolsCollection[index] is Clef); --index) ;
            symbolsCollection[index].RaiseEnvironmentChanged();
        }

        /// <summary>
        /// Restores a previously removed clef.
        /// </summary>
        /// <param name="clef">
        /// <para>
        /// Type: <see cref="Clef"/>
        /// </para>
        /// <para>
        /// The clef.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="clef"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="clef"/> does not belong to this staff.
        /// </exception>
        public void RestoreClef(Clef clef)
        {
            if (clef == null)
                throw new ArgumentNullException("clef");
            if (clef.FindOwner<Staff>() != this)
                throw new ArgumentException("The restored clef does not belong to this staff.", "clef");

            var mindex = measuresIndex.IndexOfKey(clef.Measure);
            var startIndex = measuresIndex.Values[mindex].SymbolsIndex;
            var stopIndex = symbolsCollection.Count;
            if (mindex < measuresIndex.Count - 1)
                stopIndex = measuresIndex.Values[mindex + 1].SymbolsIndex;

            int clefIndex = startIndex;
            for (; clefIndex < stopIndex && symbolsCollection[clefIndex].Beat.Beginning < clef.Beat.Beginning; ++clefIndex) ;
            symbolsCollection.Insert(clefIndex, clef);

            for (int i = mindex + 1; i < measuresIndex.Count; ++i)
                ++measuresIndex.Values[i].SymbolsIndex;

            clef.EnvironmentChanged += new EventHandler(Symbol_EnvironmentChanged);
            clef.RaiseEnvironmentChanged();
        }

        /// <summary>
        /// Restores a given durational symbol.
        /// </summary>
        /// <remarks>
        /// This method is used as a temporary solution to the following scenario:
        /// <list type="number">
        /// <item>
        /// The user invokes a command for inserting a new durational symbol.
        /// This creates a new element in the composition data and automatically in all the affected staves.
        /// </item>
        /// <item>
        /// The user invokes a command for modifiying the properties of the previously inserted symbol.
        /// </item>
        /// <item>
        /// The user undoes both commands.
        /// </item>
        /// <item>
        /// The user redoes the insertion command.
        /// This re-inserts the removed element in the composition data, but since the affected staves are updated
        /// automatically and the collections are read-only, new representations (unrelated to those created upon the first insertion)
        /// are created.
        /// </item>
        /// <item>
        /// The user redoes the second command.
        /// However, since the command owns references to the old representations, the user does not perceive the command's effect.
        /// </item>
        /// </list>
        /// This method solves the problem by allowing the command to replace the created representation by an existing one.
        /// </remarks>
        /// <param name="symbol">
        /// <para>
        /// Type: <see cref="DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The restored symbol.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="symbol"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="symbol"/> does not belong to this staff.<br/>
        /// -or-<br/>
        /// The staff does not contain a symbol that could be replaced by the restored <paramref name="symbol"/>.
        /// </exception>
        public void RestoreSymbol(DurationalSymbol symbol)
        {
            if (symbol == null)
                throw new ArgumentNullException("symbol");
            if (symbol.FindOwner<Staff>() != this)
                throw new ArgumentException("The restored symbol does not belong to this staff.", "symbol");

            var replaced = FindSymbolFor(symbol.PresentedElement);
            if (replaced == null)
                throw new ArgumentException("The staff does not contain any symbol that could be replaced by the restored symbol.", "symbol");
            symbolsCollection[IndexOf(replaced)] = symbol;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the collection of attachments.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyCollection{T}"/> of <see cref="Attachment"/>
        /// </para>
        /// <para>
        /// The collection of attachments.
        /// </para>
        /// </value>
        public ElementCollection<Attachment> Attachments
        {
            get { return attachments; }
        }

        /// <summary>
        /// Gets or sets the color of the staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Color"/>
        /// </para>
        /// <para>
        /// The color of the staff lines.
        /// </para>
        /// </value>
        public Color Color
        {
            get { return Color.Parse(colorAttribute.Value); }
            set
            {
                colorAttribute.Value = value.ToString();
                RaisePropertyChanged("Color");
            }
        }

        /// <summary>
        /// Gets a reference to the descriptor of this staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The descriptor of this staff.
        /// </para>
        /// </value>
        public StaffDescriptor Descriptor
        {
            get { return descriptor; }
        }

        /// <summary>
        /// Gets the first clef in the staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Clef"/>
        /// </para>
        /// <para>
        /// The first clef in the staff (overrides <see cref="Symbols"/>[0]).
        /// This clef is always the first symbol of the <see cref="Symbols"/> collection.
        /// </para>
        /// <para>
        /// When a new staff is created, it first attempts to load the clef from
        /// its left logical neighbour staff. If it fails, it creates a treble-clef
        /// as the default clef.
        /// </para>
        /// <para>
        /// To change the first clef of the staff, change the properties of this clef.
        /// It cannot be removed or replaced.
        /// </para>
        /// </value>
        public Clef FirstClef
        {
            get { return (Clef)symbolsCollection[0]; }
        }

        /// <summary>
        /// Gets the first key signature in the staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="KeySignature"/>
        /// </para>
        /// <para>
        /// The first key signature in the staff (overrides <see cref="Symbols"/>[1]).
        /// This key signature is always the second symbol of the <see cref="Symbols"/> collection.
        /// </para>
        /// <para>
        /// When a new staff is created, it first attempts to load the key signature from
        /// its left logical neighbour staff. If it fails, it creates a C major / a minor
        /// key signature (no accidentals) as the default key signature.
        /// </para>
        /// <para>
        /// To change the first key signature of the staff, change the properties
        /// of this key signature. It cannot be removed or replaced.
        /// </para>
        /// </value>
        public KeySignature FirstKeySignature
        {
            get { return (KeySignature)symbolsCollection[1]; }
        }

        /// <summary>
        /// Gets the first time signature in the staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="TimeSignature"/>
        /// </para>
        /// <para>
        /// The first time signature in the staff if the staff has a time signature.
        /// </para>
        /// </value>
        public TimeSignature FirstTimeSignature
        {
            get { return (symbolsCollection.Count > 2 && symbolsCollection[2] is TimeSignature ? (TimeSignature)symbolsCollection[2] : null); }
        }

        /// <summary>
        /// Gets or sets the height of the staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The height (in centimeters) of the staff.
        /// The height is the distance between the top and bottom staff line.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> is less than 0.0.
        /// </exception>
        public double Height
        {
            get { return double.Parse(heightAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                ValidateAndThrow("Height", value);

                heightAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);

                var lines = descriptor.Lines;
                if (lines > 1)
                    spaceSize = value / (lines - 1);
                else
                    spaceSize = 0.0;

                RaisePropertyChanged("Height");
                RaisePropertyChanged("SpaceSize");
            }
        }       

        /// <summary>
        /// Gets the size of one space of the staff.
        /// </summary>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The size of one staff space (i.e. the distance between two consecutive staff lines).
        /// </para>
        public double SpaceSize
        {
            get { return spaceSize; }
        }

        /// <summary>
        /// Gets the collection of core symbols on this staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyCollection{T}"/> of <see cref="CoreSymbol"/>
        /// </para>
        /// <para>
        /// The collection of core symbols on this staff. The collection is ordered from the leftmost symbol
        /// to the rightmost one.
        /// </para>
        /// </value>
        public ReadOnlyCollection<CoreSymbol> Symbols
        {
            get { return symbols; }
        }

        /// <summary>
        /// Gets or sets the position of the top staff line.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The position of the topmost staff line.
        /// The position is given as the offset (in centimeters) of the topmost staff line from the top
        /// of the system that owns the staff.
        /// </para>
        /// </value>
        public double Top
        {
            get { return double.Parse(topAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                topAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);
                RaisePropertyChanged("Top");
            }
        }

        #endregion

        #region Private Types

        /// <summary>
        /// Holds one <see cref="measuresIndex"/> item.
        /// </summary>
        private class MeasuresIndexItem
        {
            #region Public Properties

            /// <summary>
            /// Gets or sets the environment on the beginning of the measure.
            /// </summary>
            public Environment Env
            {
                get;
                set;
            }

            /// <summary>
            /// Gets or sets the index to the <see cref="Symbols"/> collection
            /// where the measure starts
            /// </summary>
            public int SymbolsIndex
            {
                get;
                set;
            }

            #endregion
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Attachments"/> property.
        /// </summary>
        private ElementCollection<Attachment> attachments;
        /// <summary>
        /// Holds the XML element that holds the <see cref="attachments"/> collection.
        /// </summary>
        private XmlElement attachmentsElement;
        /// <summary>
        /// Holds the value of the <see cref="Color"/> property.
        /// </summary>
        private XmlAttribute colorAttribute;
        /// <summary>
        /// Holds the value of the <see cref="Descriptor"/> property.
        /// </summary>
        private StaffDescriptor descriptor;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="Descriptor"/> property.
        /// </summary>
        private XmlAttribute descriptorAttribute;
        /// <summary>
        /// Holds the value of the <see cref="Height"/> attribute.
        /// </summary>
        private XmlAttribute heightAttribute;
        /// <summary>
        /// Holds an index mapping measures to their starting indexes in the <see cref="Symbols"/> collection.
        /// </summary>
        private SortedList<MeasureElement, MeasuresIndexItem> measuresIndex;
        /// <summary>
        /// Holds the descriptor of the part that owns this staff.
        /// </summary>
        private XmlAttribute partDescriptorAttribute;
        /// <summary>
        /// Holds the value of the <see cref="SpaceSize"/> property.
        /// </summary>
        private double spaceSize;
        /// <summary>
        /// Holds the value of the <see cref="Symbols"/> property.
        /// </summary>
        private ReadOnlyCollection<CoreSymbol> symbols;
        /// <summary>
        /// Holds the collection of core symbols.
        /// </summary>
        private ElementCollection<CoreSymbol> symbolsCollection;
        /// <summary>
        /// Holds the XML element that holds the <see cref="symbols"/> collection.
        /// </summary>
        private XmlElement symbolsElement;
        /// <summary>
        /// Holds the value of the <see cref="Top"/> property.
        /// </summary>
        private XmlAttribute topAttribute;

        #endregion
    }
}
